﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////
//#define ROUTER_TRACE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MTFOrg.MTF.MTFRuntime;
using MTFOrg.MTF.MTFCore.Messages;
using System.Diagnostics;

namespace MTFOrg.MTF.MTFCore
{
    /// <summary>
    /// Class implementing the router object.
    /// </summary>
    partial class Router
    {
#if ROUTER_TRACE
        const string TRACE_OBJ = "";
#endif
        /// <summary>
        /// Descriptor for a queued query.
        /// </summary>
        protected class QueryDescriptor
        {
            public int slot;
            public object tag;

            public QueryDescriptor(int slot, object tag)
            {
                this.slot = slot;
                this.tag = tag;
            }
        }

        /// <summary>
        /// Holds state for a single element of the router.
        /// </summary>
        protected class RouterState
        {
            /// <summary>
            /// Array of flags indicating whether each link is currently queried.
            /// </summary>
            protected bool[] m_LinkIsQueried;

            /// <summary>
            /// Records the number of events that are currently active for each link.
            /// </summary>
            protected int[] m_NumEvents;

            /// <summary>
            /// Queue of queries received at the producer end.
            /// </summary>
            protected Queue<QueryDescriptor> m_QueuedQueries;

            /// <summary>
            /// Queue of events received at the consumer end.
            /// </summary>
            protected Queue<RouterQueuedEvent> m_QueuedEvents;

            /// <summary>
            /// Overlay depth.
            /// </summary>
            protected int m_OverlayDepth;

            /// <summary>
            /// Gets the array of flags indicating query status.
            /// </summary>
            public bool[] LinkIsQueried
            {
                get { return m_LinkIsQueried; }
            }

            /// <summary>
            /// Gets the number of events active on each link.
            /// </summary>
            public int[] NumEvents
            {
                get { return m_NumEvents; }
            }

            /// <summary>
            /// Gets the queue of queries received at the producer end.
            /// </summary>
            public Queue<QueryDescriptor> QueuedQueries
            {
                get { return m_QueuedQueries; }
            }

            /// <summary>
            /// Gets the queue of events received at the consumer end.
            /// </summary>
            public Queue<RouterQueuedEvent> QueuedEvents
            {
                get { return m_QueuedEvents; }
            }

            /// <summary>
            /// Gets the overlay depth.
            /// </summary>
            public int OverlayDepth
            {
                get { return m_OverlayDepth; }
            }

            /// <summary>
            /// Constructor.
            /// </summary>
            public RouterState(int numLinks, int overlayDepth)
            {
                m_OverlayDepth = overlayDepth;
                m_QueuedQueries = new Queue<QueryDescriptor>();
                m_QueuedEvents = new Queue<RouterQueuedEvent>();
                m_LinkIsQueried = new bool[numLinks];
                m_NumEvents = new int[numLinks];
            }
        }

        /// <summary>
        /// Holds information relating to a router event.
        /// </summary>
        protected class RouterEventInfo
        {
            /// <summary>
            /// Consumer link number.
            /// </summary>
            protected int m_ConsumerLinkNum;

            /// <summary>
            /// Producer link number.
            /// </summary>
            protected int m_ProducerLinkNum;

            /// <summary>
            /// Represents no link set.
            /// </summary>
            public const int NO_LINK = -1;

            /// <summary>
            /// Gets and sets the consumer link number.
            /// </summary>
            public int ConsumerLinkNum
            {
                get { return m_ConsumerLinkNum; }
                set { m_ConsumerLinkNum = value; }
            }

            /// <summary>
            /// Gets and sets the producer link number.
            /// </summary>
            public int ProducerLinkNum
            {
                get { return m_ProducerLinkNum; }
                set { m_ProducerLinkNum = value; }
            }

            /// <summary>
            /// Constructor.
            /// </summary>
            public RouterEventInfo()
            {
                m_ConsumerLinkNum = NO_LINK;
                m_ProducerLinkNum = NO_LINK;
            }
        }

        /// <summary>
        /// Holds information associated with a queued event.
        /// </summary>
        protected class RouterQueuedEvent
        {
            /// <summary>
            /// Event.
            /// </summary>
            protected Event m_Event;

            /// <summary>
            /// Consumer link number.
            /// </summary>
            protected int m_ConsumerLinkNum;

            protected int m_ConsumerSlot;

            /// <summary>
            /// Gets the event.
            /// </summary>
            public Event Event
            {
                get { return m_Event; }
            }

            /// <summary>
            /// Gets the consumer link number.
            /// </summary>
            public int ConsumerLinkNum
            {
                get { return m_ConsumerLinkNum; }
            }

            public int ConsumerSlot
            {
                get { return m_ConsumerSlot; }
                set { m_ConsumerSlot = value; }
            }

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="evnt">Event</param>
            /// <param name="consumerLinkNum">Consumer link number</param>
            public RouterQueuedEvent(Event evnt, int consumerLinkNum, int consumerSlot)
            {
                m_Event = evnt;
                m_ConsumerLinkNum = consumerLinkNum;
                m_ConsumerSlot = consumerSlot;
            }
        }

        /// <summary>
        /// Overlay depth.
        /// </summary>
        protected int m_OverlayDepth;

        /// <summary>
        /// Invoked to initialize this task.
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();

            m_OverlayDepth = OverlayDepth;

            // Hook up to the producer-consumer event notifications
            m_ProducerConsumerTask.QueryReceived += new ProducerConsumer.OnQuery(OnQueryReceived);
            m_ProducerConsumerTask.ReleaseReceived += new ProducerConsumer.OnRelease(OnReleaseReceived);
            m_ProducerConsumerTask.EventReceived += new ProducerConsumer.OnEvent(OnEventReceived);
            m_ProducerConsumerTask.ReleasedReceived += new ProducerConsumer.OnReleased(OnReleasedReceived);
        }

        /// <summary>
        /// Handles received query.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnQueryReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, ref object elementState, int elementNumber)
        {
#if ROUTER_TRACE
			string statii = string.Empty;
			if (consumerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumConsumerLinks; _i++)
					statii += " " + (
                        slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
			if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query router link" + statii, elementNumber);
#endif
            // Get router state (and create if it doesn't exist yet)
            RouterState routerState = elementState as RouterState;
            if (routerState == null)
            {
                elementState = routerState = new RouterState(m_ProducerConsumerTask.NumConsumerLinks, m_OverlayDepth);
            }

            if (routerState.QueuedEvents.Count > 0)
            {
                // Event is queued.  Dequeue and send.
                RouterQueuedEvent queuedEvent = routerState.QueuedEvents.Dequeue();

                // Create event info and store producer link number
                RouterEventInfo routerEventInfo = new RouterEventInfo();
                routerEventInfo.ProducerLinkNum = producerLinkNum;
                routerEventInfo.ConsumerLinkNum = queuedEvent.ConsumerLinkNum;
                slots.SetProducerTag(producerSlot, routerEventInfo);

                m_ProducerConsumerTask.LinkConsumerAndProducerSlots(slots, queuedEvent.ConsumerSlot, producerSlot);

                // Send queued event on with this query
                m_ProducerConsumerTask.SendEvent(producerLinkNum, producerSlot, queuedEvent.Event, elementNumber);
#if ROUTER_TRACE
                Debug.Print("{0}[{1}] link {2}", Name, elementNumber, producerLinkNum);
#endif
            }
            else
            {
                // No queued event.  Create event info and store producer link number
                RouterEventInfo routerEventInfo = new RouterEventInfo();
                routerEventInfo.ProducerLinkNum = producerLinkNum;
                slots.SetProducerTag(producerSlot, routerEventInfo);

                // Queue query
                routerState.QueuedQueries.Enqueue(new QueryDescriptor(producerSlot, routerEventInfo));

                // Find first enabled link
                IConnection firstConnection = m_ProducerConsumerTask.GetConsumerConnectionFromLinkNumber(0);
                int link = 0;
                while (link < m_ProducerConsumerTask.NumConsumerLinks &&
                    ConnectionHelper.IsConsumerLinkDisabled(firstConnection, elementNumber, link))
                    link++;
                
                // Query all unqueried consumer links with num_events < overlay_depth.
                while (link < m_ProducerConsumerTask.NumConsumerLinks)
                {
                    if (!routerState.LinkIsQueried[link] && routerState.NumEvents[link] < routerState.OverlayDepth)
                    {
                        if (m_ProducerConsumerTask.SendQuery(link, producerSlot, elementNumber))
                        {
                            routerState.LinkIsQueried[link] = true;
#if ROUTER_TRACE
                            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query sent on router link {1} order {2}", elementNumber, link, 0);
#endif
                        }
                    }

                    // Next enabled link
                    link++;
                    while (link < m_ProducerConsumerTask.NumConsumerLinks &&
                        ConnectionHelper.IsConsumerLinkDisabled(firstConnection, elementNumber, link))
                        link++;
                }
            }
        }

        /// <summary>
        /// Handles received release.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="eventToRelease">The event to be released</param>
        /// <param name="isRollback">Whether the release was a roll-back</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        /// <returns>Whether to complete the current slot set</returns>
        public bool OnReleaseReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventToRelease, bool isRollback, bool deletedSlot, ref object elementState, int elementNumber)
        {
#if ROUTER_TRACE
			string statii = string.Empty;
			if (consumerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumConsumerLinks; _i++)
					statii += " " + (
                        slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Release router link" + statii, elementNumber);
#endif
            // Get the event info associated with the released producer slot-set.
            RouterEventInfo routerEventInfo = slots.GetProducerTag(producerSlot) as RouterEventInfo;
            RouterState routerState = elementState as RouterState;

            // Send release on associated consumer link
#if ROUTER_TRACE
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Release router link {1}", elementNumber, routerEventInfo.ConsumerLinkNum);
#endif
            m_ProducerConsumerTask.SendRelease(consumerSlot, eventToRelease, isRollback, routerEventInfo.ConsumerLinkNum, elementNumber, true);

            // Increment event count
            routerState.NumEvents[routerEventInfo.ConsumerLinkNum]--;

            return true;
        }

        /// <summary>
        /// Called when the producer notifies it has finished with an event.
        /// </summary>
        /// <param name="consumerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnReleasedReceived(int consumerLinkNum, ref object elementState, int elementNumber)
        {
            // Handle any queued queries
            RouterState routerState = elementState as RouterState;
            if (routerState.QueuedQueries.Count > 0)
            {
                if (routerState.QueuedQueries.Count > 0)
                {
                    QueryDescriptor nextQuery = routerState.QueuedQueries.Peek();

                    // Get the event info associated with the released producer slot-set.
                    RouterEventInfo routerEventInfo = nextQuery.tag as RouterEventInfo;

                    // Find first enabled link
                    IConnection firstConnection = m_ProducerConsumerTask.GetConsumerConnectionFromLinkNumber(0);
                    int link = 0;
                    while (link < m_ProducerConsumerTask.NumConsumerLinks &&
                        ConnectionHelper.IsConsumerLinkDisabled(firstConnection, elementNumber, link))
                        link++;

                    // Query all unqueried consumer links with num_events < overlay_depth.
                    while (link < m_ProducerConsumerTask.NumConsumerLinks)
                    {
                        if (!routerState.LinkIsQueried[link] &&
                            routerState.NumEvents[link] < routerState.OverlayDepth)
                        {
                            if (m_ProducerConsumerTask.SendQuery(link, nextQuery.slot, elementNumber))
                            {
                                routerState.LinkIsQueried[link] = true;
#if ROUTER_TRACE
                                if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query sent on router link {1} order {2}", elementNumber, link, 0);
#endif
                            }
                        }

                        // Next enabled link
                        link++;
                        while (link < m_ProducerConsumerTask.NumConsumerLinks &&
                            ConnectionHelper.IsConsumerLinkDisabled(firstConnection, elementNumber, link))
                            link++;
                    }
                }
            }
        }

        /// <summary>
        /// Called when the ProducerConsumer receives an event
        /// </summary>
        /// <param name="consumerLinkNum">Consumer link (absolute) on which the event arrived</param>
        /// <param name="consumerSlots">Consumer slot state information</param>
        /// <param name="producerSlots">Producer slot state information</param>
        /// <param name="eventReceived">Event that was received</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnEventReceived(int consumerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventReceived, ref object elementState, int elementNumber)
        {
#if ROUTER_TRACE
			string statii = string.Empty;
			if (consumerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumConsumerLinks; _i++)
					statii += " " + (
                        slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetConsumerLinkStatus(consumerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Event on router link {1}" + statii, elementNumber, consumerLinkNum);
#endif

            // Get the router state
            RouterState routerState = elementState as RouterState;

            // Increment event count
            routerState.NumEvents[consumerLinkNum]++;

            bool sentQuery = false;
            if (routerState.QueuedQueries.Count > 0)
            {
                // Get queued query
                QueryDescriptor queuedQuery = routerState.QueuedQueries.Dequeue();

                // Store consumer link number in the producer slots tag
                RouterEventInfo routerEventInfo = queuedQuery.tag as RouterEventInfo;
                routerEventInfo.ConsumerLinkNum = consumerLinkNum;

                m_ProducerConsumerTask.LinkConsumerAndProducerSlots(slots, consumerSlot, queuedQuery.slot);

                // Produce event
                m_ProducerConsumerTask.SendEvent(routerEventInfo.ProducerLinkNum, queuedQuery.slot, eventReceived, elementNumber);
#if ROUTER_TRACE
                Debug.Print("{0}[{1}] link {2}", Name, elementNumber, 0);
#endif

                // If another query is queued
                if (routerState.QueuedQueries.Count > 0)
                {
                    // If number of events < overlay depth
                    if (!routerState.LinkIsQueried[consumerLinkNum] &&
                        routerState.NumEvents[consumerLinkNum] < routerState.OverlayDepth)
                    {
                        QueryDescriptor nextQuery = routerState.QueuedQueries.Peek();

                        // Try to send query on current link
                        if (m_ProducerConsumerTask.SendQuery(consumerLinkNum, nextQuery.slot, elementNumber))
                        {
                            sentQuery = true;
                            routerState.QueuedQueries.Dequeue();
#if ROUTER_TRACE
                            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query sent on router link {1} order {2}", elementNumber, consumerLinkNum, 0);
#endif
                        }
                    }
                }
            }
            else
            {
                // Queue the event
                routerState.QueuedEvents.Enqueue(new RouterQueuedEvent(eventReceived, consumerLinkNum, consumerSlot));
            }

            routerState.LinkIsQueried[consumerLinkNum] = sentQuery;
        }
    }
}
